接續前面兩篇的內容,今天筆者將來實作ChangeNotifier,並讓應用程式在增加新的Todo物件時,畫面會跟著一起更新,顯示新加入的內容。因為這部份的概念於前兩篇中已經談過了,因此本篇將不會有太詳細的說明。
因在此想追蹤儲存Todo的資料庫是否有內容變動,因此我們的data/models/blocks.dart
中的BlocksDb
須設定為ChangeNotifier的子類別(child class)。至於child_blocks.dart檔案中的內容本次則不需要修改,因其中的類型都是blocks.dart中的子類別。
更改後的程式碼如下:
abstract class BlocksDb extends ChangeNotifier {
String dbFilename, tableName, executeSQL;
bool dbIsOpen = false;
late var db;
BlocksDb(
{required this.dbFilename,
required this.tableName,
required this.executeSQL});
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future open() async {
db = await openDatabase(
join(await _localPath, dbFilename),
//dbFilename,
version: 1,
onOpen: (db) {
dbIsOpen = true;
},
onCreate: (db, version) async {
await db.execute('''CREATE TABLE $tableName ($executeSQL)''');
},
);
notifyListeners();
return null;
}
Future<Block> insert(Block block) async {
if (!dbIsOpen) open();
block.id = await db.insert(tableName, block.toMap());
notifyListeners();
return block;
}
Future<int> delete(int id) async {
if (!dbIsOpen) open();
var idx = await db.delete(tableName, where: 'id = ?', whereArgs: [id]);
notifyListeners();
return idx;
}
Future<int> update(Block aBlock) async {
if (!dbIsOpen) open();
var idx = await db.update(tableName, aBlock.toMap(),
where: 'id = ?', whereArgs: [aBlock.id]);
notifyListeners();
return idx;
}
Future close() async {
dbIsOpen = false;
return await db.close();
}
Future<int> getCount() async =>
await db.execute('SELECT COUNT(*) from $tableName');
}
這部份的變動十分簡單,只需要改以下部份:
class BlocksDb extends ChangeNotifier
=> 改為子類別notifyListeners()
(open(), insert(), delete(), update())很可能會有人疑惑,ChangeNotifierProvider該加在哪裡會比較適當呢?那便是程式越末端,離Consumer越近的上方位置越好,如此便可以避免太多物件因ChangeNotifier的通知而受到影響。至於Consumer,當然則是讓它盡可能只包含需要更新的widget,也就是todo頁面中的清單。
以下為更改後的程式碼:
class _TodoPageState extends State<TodoPage> {
late Future<List<TimeBlock>?> dbList;
var num = 0;
@override
Widget build(context) {
return ChangeNotifierProvider(
create: (context) => TimeBlocksDb(),
child: Consumer<TimeBlocksDb>(builder: (context, db, child) {
dbList = db.getAll();
return Scaffold(
body: FutureBuilder<List<TimeBlock>?>(
builder: (con, snap) {
if (snap.hasData) {
return ListView.builder(
itemCount: snap.data!.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(snap.data![index].name.toString()),
onTap: () {});
},
);
} else {
return const Center(child: Text("Waiting..."));
}
},
future: dbList,
),
floatingActionButton: FloatingActionButton(
// add new timeblock
onPressed: () {
db.insert(TimeBlock.name('name_$num'));
print('name_$num\n');
num++;
},
child: const Icon(Icons.add),
),
);
}),
);
}
}
由上方可以看到,原本Todo頁面僅有的widget: Scaffold現在被包在Consumer之內,而Consumer又被包在ChangeNotifierProvider內部。並且,程式碼中所有的TimeBlocksDb物件皆改為使用Consumer的建構式中所設的db,而非_TodoPageState的property: TimeBlocksDb db = TimeBlocksDb();
謝謝閱讀到這裡的讀者。有任何問題或想說的都歡迎留言或email,明天將會繼續努力更新的!
email: nnyjan02426@gmail.com
github: nnyjan02426
Schedrag: nnyjan02426/Schedrag